#include "Application.h"
#include "Core/Color4.h"
#include "Core/Point2.h"
#include "Core/Rect2.h"
#include "Core/glExt.h"
#include "Core/Vector3.h"
#include "Core/Vector2.h"
#include "Core/Transform.h"
#include "Core/Quaternion.h"
#include "Model/MeshFactory.h"
#include "Model/Mesh.h"
#include "View/RenderContext.h"
#include "Core/IoQuaternion.h"
#include "GUI/Button.h"
#include <cassert>
#include <algorithm>
#include <functional>
#include "Core/GlPlatform.h"
#include "Model/RobotMesh.h"

// Constructors
Application::Application() : selectedObject(0), wireframe(false) {
	std::cout 
		<< " q/w/e and 1/2/3  Rotate object pitch, yaw, and roll respectively\n"
		   " a/s/d and z/x/c  Scale object x-axis, y-axis, and z-axis respectively\n"
		   " i/j/k/l          Move object: j/l, x-axis; i/k y-axis; u/o, z-axis\n"
		   "\n"
		   " [/]              Remove and add object respectively\n"
		   " -/=              Select next object\n"
		   " f                Toggle wireframe on object\n"
	;

	glutInitWindowSize( 800, 600 );
	glutInitWindowPosition( -1, -1 );
	
	glutCreateWindow( "Team C - Group Assignment #4" );
	instanceMap.insert( std::pair<int,Application* const>( glutGetWindow(), this ) );

	glutReshapeFunc( reshape );
	glutKeyboardFunc( keyboard );
	glutMouseFunc( mouse );
	glutDisplayFunc( render );
	glutMotionFunc( motion );
	setupMenu();
	setupGui();
	
	static const float h = .5f;
	using namespace Core;
	using namespace Model;
	// auto box = factory.createBox( Transform3f( Point3f( 0, 0, -5 ) ), Vector3f(1,1,1) );
	// box->mSubmeshes.push_back( factory.createBox( Transform3f( Point3f( -2, 0, 0 ) ), Vector3f(h,h,h) ) );
	// box->mSubmeshes.push_back( factory.createBox( Transform3f( Point3f(  2, 0, 0 ) ), Vector3f(h,h,h) ) );
	scene.addObject( factory.createTable( Transform3f( Point3f(0, .5, -5) ), Vector3f( 1, 0.5f, 0.75f ), 0.125f, Vector2f( 0.125f, 0.125f ), Vector2f( 0.0625f, 0.0625 ) ) );
	tansformMode = 0;
	// scene.addObject( box );
}


Application::~Application() {
	int id = glutGetWindow();
	size_t ct = instanceMap.erase(id);
	assert( ct == 1 );
	glutDestroyWindow(id);
}

// Members
void Application::run() {
}

// Callbacks
void Application::onRender() {
	using namespace Core;
	using namespace Model;

	glClearColor( .2, .2, .2, 1 );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    glEnable( GL_BLEND );
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); 
	
	glEnable( GL_DEPTH_TEST );
	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
	glPushMatrix(); {
		View::RenderContext rx = { wireframe };
		scene.render( rx );
	} glPopMatrix();

	glDisable( GL_DEPTH );

	glDepthMask( false );
	glDisable( GL_LIGHTING );
	glDisable( GL_DEPTH_TEST );
	glMatrixMode( GL_PROJECTION );
	glPushMatrix(); {
		glLoadIdentity();

		glMatrixMode( GL_MODELVIEW ); {
			glLoadIdentity();
			glOrtho( 0, screenWidth, screenHeight, 0, 0, 10 );
			panel->render();
		} glMatrixMode( GL_PROJECTION );
	} glPopMatrix();
	glDepthMask( true );
	glEnable( GL_DEPTH_TEST );

	glutSwapBuffers();
}

void Application::onKeyboard( unsigned char ch, int x_, int y_ ) {
	float x, y, z;
	x = y = z = 0;
	switch ( ch ) {
	case 'w': y =  1; break;
	case 's': y = -1; break;
	case 'd': x =  1; break;
	case 'a': x = -1; break;
	case 'q': z =  1; break;
	case 'e': z = -1; break;
	default:
		return;
	}
	glutPostRedisplay();
	if ( scene.mObjects.empty() )
		return;
	
	auto& ptr = scene.mObjects[selectedObject];
	if ( x || y || z ) {
		float ang = 3.1415926 / 50;
		Core::Quaternion4f& quat = ptr->mTransform.orientation;
		switch ( tansformMode ) {
		case 0:	ptr->mTransform.position += Core::Vector3f( x, y, z ) * .1f; break;
		case 1: quat *= Core::Quaternion4f::fromEuler( x * ang, y * ang, z * ang ); quat.normalize(); break;
		case 2: ptr->mTransform.scale += Core::Vector3f( x, y, z ) * .1f; break;
		}
	}	
}

void Application::onMotion( int x, int y ) {

}

void Application::onMouse( int button, int state, int x, int y ) {
	if ( state == GLUT_DOWN ) {
		if ( panel->onMouseDown( Core::Point2f( x, y ) ) )
			glutPostRedisplay();
	} else if ( state == GLUT_UP ) {
		if ( panel->onMouseUp( Core::Point2f( x, y ) ) )
			glutPostRedisplay();
	}
}

void Application::onMenu( int idx ) {
}

void Application::onReshape( int w, int h ) {
	screenWidth = w;
	screenHeight = h;

	glViewport(0, 0, w, h );
	
	double aspect = (double)w/h;
	double a = std::min( aspect, 1. );
	double b = std::max( aspect, 1. );
	
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();
	gluPerspective( 60., aspect, .1, 100. );

	// glOrtho( -10*b, 10*b, -10/a, 10/a, -10, 10 );

	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
}

void Application::onQuit() {
	std::cout << "onQuit\n";
}

// Static glut callback handlers, mapped to instance members
void Application::reshape( int w, int h ) {
	instanceMap[glutGetWindow()]->onReshape( w, h );
}

void Application::keyboard( unsigned char ch, int x, int y ) {
	instanceMap[glutGetWindow()]->onKeyboard( ch, x, y );
}

void Application::mouse( int button, int state, int x, int y ) {
	instanceMap[glutGetWindow()]->onMouse( button, state, x, y );
}

void Application::render() {
	instanceMap[glutGetWindow()]->onRender();
}

void Application::motion( int x, int y ) {
	instanceMap[glutGetWindow()]->onMotion( x, y );
}

void Application::menu( int idx ) {
	instanceMap[glutGetWindow()]->onMenu( idx );
}

void Application::setupGui() {
	using namespace Gui;
	using namespace Core;

	Color4 bgPanel =  Color4::gray( 0, .5f );
	Color4 bgButton = Color4::gray( .75f );
	Color4 bgButtonPressed = Color4::gray( .25f );
	Color4 fgText =   Color4::gray( 0 );
	Vector2f btnSize = Vector2f( 16*8, 15+4*2 );
	int gutterSize = 4;

	
	panel.reset( new Panel( Rect2f( 0, 0, 0, 0 ) ) );
	panel->mColor = bgPanel;

	static const struct { int col; void (Application::* fn)(); std::string txt; } buttons[] = {
		{ 0, &Application::onNewCube, "New Cube" },
		{ 0, &Application::onNewTable, "New Table" },
		{ 0, &Application::onNewRobot, "New Robot" },
		{ 0, &Application::onDeleteObject, "Delete Object" },
		{ 1, &Application::onTranslateMode, "Translate" },
		{ 1, &Application::onRotateMode, "Rotate" },
		{ 1, &Application::onScaleMode, "Scale" },
		{ 2, &Application::onNextObject, "Select Next" },
		{ 2, &Application::onPrevObject, "Select Previous" }

	};

	int curCol = 0;
	float ymax = 0;
	Rect2f totalSize = Rect2f::fromSize( 0, 0, gutterSize, gutterSize );
	Point2f pos = Point2f( gutterSize, gutterSize );
	typedef std::shared_ptr<Button> ButtonPtr;
	for each ( const auto& btn in buttons ) {
		if ( curCol != btn.col ) {
			curCol = btn.col;
			pos.y = gutterSize;
			pos.x += btnSize.x + gutterSize;
		}
		panel->mChildren.push_back( ButtonPtr( new Button(
			Rect2f::fromSize( pos.x, pos.y, btnSize.x, btnSize.y ),
			std::bind( btn.fn, this ),
			bgButton,
			fgText,
			bgButtonPressed,
			btn.txt
		) ) );
		pos.y += btnSize.y + gutterSize;
		ymax = std::max( ymax, pos.y );
	}

	panel->mClientArea = Rect2f::fromSize( 0, 0, pos.x + btnSize.x + 2*gutterSize, ymax + 2*gutterSize );
}

void Application::setupMenu() {
	glutCreateMenu(menu);
	glutAddMenuEntry("Torso Rotate", 0);
	glutAddMenuEntry("Torso Pitch", 1);
	glutAddMenuEntry("Torso Side to Side", 2);
	glutAddMenuEntry("Head Pitch", 3);
	glutAddMenuEntry("Head Rotate", 4);
	glutAddMenuEntry("Head Side to Side", 5);
	glutAddMenuEntry("Left Shoulder Pitch", 6);
	glutAddMenuEntry("Left Shoulder Rotate", 7);
	glutAddMenuEntry("Left Shoulder Flap", 8);
	glutAddMenuEntry("Left Elbow", 9);
	glutAddMenuEntry("Left Wrist", 10);
	glutAddMenuEntry("Right Shoulder Pitch", 11);
	glutAddMenuEntry("Right Shoulder Rotate", 12);
	glutAddMenuEntry("Right Shoulder Flap", 13);
	glutAddMenuEntry("Right Elbow", 14);
	glutAddMenuEntry("Right Wrist", 15);
	glutAddMenuEntry("Left Hip Pitch", 16);
	glutAddMenuEntry("Left Hip Flap", 17);
	glutAddMenuEntry("Left Knee", 18);
	glutAddMenuEntry("Left Ankle", 19);
	glutAddMenuEntry("Right Hip Pitch", 20);
	glutAddMenuEntry("Right Hip Flap", 21);
	glutAddMenuEntry("Right Knee", 22);			 
	glutAddMenuEntry("Right Ankle", 23);
	glutAddMenuEntry("Quit", 24);
	glutAttachMenu(GLUT_RIGHT_BUTTON);
}

void Application::onNewTable() {
	std::cout << "New table\n";
	using namespace Core;
	auto t = Transform3f( Point3f(0, 0, -5) );
	scene.addObject( factory.createTable(t, Vector3f( 1, 0.5f, 0.75f ), 0.125f, Vector2f( 0.125f, 0.125f ), Vector2f( 0.0625f, 0.0625 ) ) );
	selectedObject = scene.mObjects.size()-1;
}

void Application::onNewCube() {
	std::cout << "New cube\n";
	using namespace Core;
	auto t = Transform3f( Point3f(0, 0, -5) );
	scene.addObject( factory.createBox(t, Vector3f(1,1,1) ) );
	selectedObject = scene.mObjects.size()-1;
}

void Application::onNewRobot() {
	std::cout << "New robot\n";
	using namespace Core;
	auto t = Transform3f( Point3f(0, 0, -5) );
	scene.addObject( std::shared_ptr<Model::Mesh>( new Model::RobotMesh(t) ) );
	selectedObject = scene.mObjects.size()-1;
}

void Application::onDeleteObject() {
	std::cout << "On delete object\n";
	if ( scene.mObjects.empty() )
			return;
	auto ct = scene.mObjects.size();
	scene.mObjects.erase( scene.mObjects.begin()+selectedObject ); 
	if ( selectedObject && selectedObject == scene.mObjects.size() )
		selectedObject--;
}

void Application::onTranslateMode() {
	std::cout << "onTranslateMode\n";
	tansformMode = 0;
}

void Application::onRotateMode() {
	std::cout << "onRotateMode\n";
	tansformMode = 1;
}

void Application::onScaleMode() {
	std::cout << "onScaleMode\n";
	tansformMode = 2;
}

void Application::onWireframe() {
	std::cout << "onWireframe\n";
}

void Application::onNextObject() {
	std::cout << "onNextObject\n";
	auto ct = scene.mObjects.size();
	if ( ct )
		selectedObject = (selectedObject + ct-1)%ct;
}

void Application::onPrevObject() {
	std::cout << "onPrevObject\n";
	auto ct = scene.mObjects.size();
	if ( ct )
		selectedObject = (selectedObject+1)%ct;
}

// Helper members
std::map<int, Application* const> Application::instanceMap;
